﻿#include <learn/mesh/mesh3d.h>
#include <learn/topo/topo_builder.h>
#include <learn/topo/topo_explorer.h>

using namespace xi;
using namespace xi::mesh;

Mesh3D<3>::Mesh3D()
    : m_vertexId("vid"), m_vertexPos("vpos"), m_vertexTexcoord("vtexcoord"), m_vertexNormal("vnormal"),
      m_faceNormal("fnormal")
{
}

VHandle Mesh3D<3>::create_vertex(const Vec3f &pos)
{
    auto vh = Half_Triangle_Mesh::create_vertex();
    vh->add_property(m_vertexPos);
    vertex_pos(vh) = pos;
    return vh;
}

FHandle Mesh3D<3>::create_face(VHandle v0, VHandle v1, VHandle v2)
{
    auto f3 = [this](CHandle c0, CHandle c1, CHandle c2) -> FHandle { return create_face(c0, c1, c2); };

    auto f2 = [this](CHandle c0, CHandle c1) -> FHandle {
        auto c2 = create_edge(Topo_Explorer::end_vertex(c1), Topo_Explorer::start_vertex(c0));
        return create_face(c0, c1, c2);
    };

    auto f1 = [this](CHandle c0, VHandle v2) -> FHandle {
        auto c1 = create_edge(Topo_Explorer::end_vertex(c0), v2);
        auto c2 = create_edge(v2, Topo_Explorer::start_vertex(c0));
        return create_face(c0, c1, c2);
    };

    auto f0 = [this](VHandle v0, VHandle v1, VHandle v2) -> FHandle {
        auto c0 = create_edge(v0, v1);
        auto c1 = create_edge(v1, v2);
        auto c2 = create_edge(v2, v0);
        return create_face(c0, c1, c2);
    };

    CHandle c[] = {Topo_Explorer::common_coedge(v0, v1), Topo_Explorer::common_coedge(v1, v2),
                   Topo_Explorer::common_coedge(v2, v0)};

    if (c[2] == nullptr)
    {
        std::swap(c[1], c[2]);
        std::swap(c[0], c[1]);
        std::swap(v1, v2);
        std::swap(v0, v1);
    }

    if (c[2] == nullptr)
    {
        std::swap(c[1], c[2]);
        std::swap(c[0], c[1]);
        std::swap(v1, v2);
        std::swap(v0, v1);
    }

    if (c[2] == nullptr)
        return f0(v0, v1, v2);
    if (c[1] == nullptr)
        return f1(c[2], v1);
    if (c[0] == nullptr)
        return f2(c[1], c[2]);

    return f3(c[0], c[1], c[2]);
}

FHandle Mesh3D<3>::create_face(CHandle c0, CHandle c1, CHandle c2)
{
    return Half_Triangle_Mesh::create_face({c0, c1, c2});
}

std::size_t Mesh3D<3>::vertex_id(VHandle v) const
{
    return v->property(m_vertexId);
}

std::size_t &Mesh3D<3>::vertex_id(VHandle v)
{
    return v->property(m_vertexId);
}

Vec3f Mesh3D<3>::vertex_pos(VHandle v) const
{
    return v->property(m_vertexPos);
}

Vec3f &Mesh3D<3>::vertex_pos(VHandle v)
{
    return v->property(m_vertexPos);
}

Vec3f Mesh3D<3>::vertex_normal(VHandle v) const
{
    return v->property(m_vertexNormal);
}

Vec3f &Mesh3D<3>::vertex_normal(VHandle v)
{
    return v->property(m_vertexNormal);
}

Vec2f Mesh3D<3>::vertex_texcoord(VHandle v) const
{
    return v->property(m_vertexTexcoord);
}

Vec2f &Mesh3D<3>::vertex_texcoord(VHandle v)
{
    return v->property(m_vertexTexcoord);
}

Vec3f Mesh3D<3>::face_normal(FHandle f) const
{
    return f->property(m_faceNormal);
}

Vec3f &Mesh3D<3>::face_normal(FHandle f)
{
    return f->property(m_faceNormal);
}

void Mesh3D<3>::request_face_normals()
{
    for (auto f : faces())
        f->add_property(m_faceNormal);
}

void Mesh3D<3>::remove_face_normals()
{
    for (auto f : faces())
        f->remove_property(m_faceNormal);
}

void Mesh3D<3>::request_vertex_normals()
{
    for (auto v : vertices())
        v->add_property(m_vertexNormal);
}

void Mesh3D<3>::remove_vertex_normals()
{
    for (auto v : vertices())
        v->remove_property(m_vertexNormal);
}

void Mesh3D<3>::request_vertex_texcoords()
{
    for (auto v : vertices())
        v->add_property(m_vertexTexcoord);
}

void Mesh3D<3>::remove_vertex_texcoords()
{
    for (auto v : vertices())
        v->remove_property(m_vertexTexcoord);
}

void Mesh3D<3>::request_vertex_ids()
{
    for (auto v : vertices())
        v->add_property(m_vertexId);
}

void Mesh3D<3>::remove_vertex_ids()
{
    for (auto v : vertices())
        v->remove_property(m_vertexId);
}

void Mesh3D<3>::update_face_normals()
{
    for (auto f : faces())
    {
        auto vList = Topo_Explorer::vertices_on_face(f);
        auto p0 = vertex_pos(vList[0]);
        auto p1 = vertex_pos(vList[1]);
        auto p2 = vertex_pos(vList[2]);

        face_normal(f) = cross(p1 - p0, p2 - p1).normalized();
    }
}

void Mesh3D<3>::update_vertex_normals()
{
    for (auto v : vertices())
    {
        auto fList = Topo_Explorer::adjacent_faces(v);
        if (fList.empty())
        {
            vertex_normal(v) = Vec3f{0.0f, 0.0f, 0.0f};
            continue;
        }

        auto n = Vec3f{0.0f, 0.0f, 0.0f};
        for (auto f : fList)
            n += face_normal(f);

        vertex_normal(v) = n.normalized();
    }
}